package sjtu.ChatClient;

import genDoc.StopVideoRequestDocGen;
import genDoc.StoppedVideoChatDocGen;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Semaphore;

import org.jdom.Document;

import com.neurosky.thinkgear.TGDevice;

import command.CommandConstants;

import threads.DecodeImageThread;
import threads.DecodeImageWorker;
import threads.PlayAudioThread;
import threads.ReceiveAttentionThread;
import threads.SendAudioThread;
import net.ImageOutputStream;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.os.Build.VERSION;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ImageView;
import android.widget.Toast;
import android.os.Vibrator;

public class VideoChatActivity extends Activity {
	private static Camera frontCamera;
	private LinearLayout frameLayout;
	private CameraPreview cameraPreview;
	private boolean shouldSendImage = false;
	private Timer timer;
	private Semaphore imgSem;
	private DecodeImageThread decodeImageThread;
	private ImageView imageView;
	private String to;
	private boolean firstTearDown = true;
	private static int frontCameID;
	private PlayAudioThread playAudioThread;
	private SendAudioThread sendAudioThread;
	
	private BluetoothAdapter bluetoothAdapter = null;
	private TGDevice tgDevice = null;
	private ImageView attentionIV;
	
	private Vibrator vibrator;
	
	public static Camera getFrontCamera(){
		return VideoChatActivity.frontCamera;
	}
	
	private WakeLock wakeLock;
	
	private ReceiveAttentionThread receiveAttentionThread;
	private DatagramSocket attentionSocket;
	private byte [] attentionBuffer;
	
	// check if the front camera is available
	public static boolean checkFrontCamera(Context context){
		if(VERSION.SDK_INT < 9){
			return false;
		}
		
		PackageManager pm = context.getPackageManager();
		if(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) && pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT) && pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS)){
			return true;
		} else {
			return false;
		}
	}
	
	public static void initFrontCameraInstance(Context context){
		if(checkFrontCamera(context)){
			int cameraCount = Camera.getNumberOfCameras();
			Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
			int i = 0;
			for(i = 0;i < cameraCount;i++){
				Camera.getCameraInfo(i, cameraInfo);
				if(cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){
					try{
						frontCamera = Camera.open(i);
						frontCameID = i;
						//Camera.Parameters parameters = frontCamera.getParameters();
						//parameters.setPreviewSize(80, 120);
						//frontCamera.setParameters(parameters);
					} catch (RuntimeException e){
						e.printStackTrace();
					}
				}
			}
		}
	}
	
	protected void onCreate(Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
		setContentView(R.layout.activity_video_chat);
		
		to = getIntent().getExtras().getString(CommandConstants.ATTR_USER_NAME);
		
		frameLayout = (LinearLayout) this.findViewById(R.id.frameLayout);
		VideoChatActivity.initFrontCameraInstance(VideoChatActivity.this);
		CameraPreview.setCameraDisplayOrientation(this, frontCameID, frontCamera);
		frontCamera.setPreviewCallback(previewCallback);
		cameraPreview = new CameraPreview(VideoChatActivity.this, frontCamera);
		frameLayout.addView(cameraPreview);
		frameLayout.setVisibility(View.VISIBLE);
		
		imageView = (ImageView) this.findViewById(R.id.imageView);
		attentionIV = (ImageView) this.findViewById(R.id.attentionIV);
		attentionIV.setBackgroundResource(R.drawable.emo_happy);
		
		wakeLock = ((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "VideoSenderMainActivity");
		imgSem = new Semaphore(1);
		
		initSendImageTimer();
		
		decodeImageThread = new DecodeImageThread(SystemConstants.VIDEO_PORT, handler);
		new Thread(decodeImageThread).start();
		
		playAudioThread = new PlayAudioThread(SystemConstants.VOICE_PORT);
		new Thread(playAudioThread).start();
		sendAudioThread = new SendAudioThread(StateManager.getManager().getUserIP(to), SystemConstants.VOICE_PORT);
		new Thread(sendAudioThread).start();
		
		this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		if(this.bluetoothAdapter == null){
			Toast.makeText(getBaseContext(), "Bluetooth not available!", Toast.LENGTH_LONG).show();
			finish();
			return;
		} else {
			this.tgDevice = new TGDevice(this.bluetoothAdapter, handler);
		}
		
		receiveAttentionThread = new ReceiveAttentionThread(handler);
		new Thread(receiveAttentionThread).start();
		
		this.attentionSocket = null;
		attentionBuffer = new byte[4];
		
		vibrator = (Vibrator) getSystemService(Service.VIBRATOR_SERVICE);
	}
	
	public void onDestroy(){
		shouldSendImage = false;
		timer.cancel();
		timer.purge();
		frontCamera.setPreviewCallback(null);
		frontCamera.stopPreview();
		frontCamera.release();
		decodeImageThread.stop();
		playAudioThread.stop();
		sendAudioThread.stop();
		receiveAttentionThread.stop();
		
		Document doc = null;
		if(firstTearDown){
			doc = StopVideoRequestDocGen.genDoc(StateManager.getManager().getLocalUserName(), to);
		} else {
			doc = StoppedVideoChatDocGen.genDoc(StateManager.getManager().getLocalUserName(), to);
		}
		StateManager.getManager().getDataSender().add(doc);
	
		this.tgDevice.close();
		super.onDestroy();
	}
	
	/*
	 *  every FRAME_DELAY miliseconds, set shouldImage to true, send an image
	 */
	private void initSendImageTimer(){
		timer = new Timer("FrameTimer", false);
		TimerTask task = new TimerTask(){
			public void run(){
				shouldSendImage = true;
			}
		};
		
		timer.schedule(task, SystemConstants.FRAME_DELAY, SystemConstants.FRAME_DELAY);
	}
	
	private Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
		
		@Override
		public void onPreviewFrame(byte[] data, Camera camera) {
			// TODO Auto-generated method stub
			if(shouldSendImage){
				ImageOutputStream iout = new ImageOutputStream(data.length);
				Camera.Parameters param = frontCamera.getParameters();
				YuvImage yui = new YuvImage(data, ImageFormat.NV21, param.getPreviewSize().width, param.getPreviewSize().height, null);
				Rect rect = new Rect(0, 0, param.getPreviewSize().width, param.getPreviewSize().height);
				yui.compressToJpeg(rect, SystemConstants.IMAGE_QUALITY, iout);
				
				System.err.println("size = " + iout.getSize());
				
				iout.sendImage(StateManager.getManager().getUserIP(to), SystemConstants.VIDEO_PORT);
				
				shouldSendImage = false;
			}
		}
	};
	
	public void onResume(){
		super.onResume();
		
		StateManager.getManager().setCurrentHandler(handler);
		wakeLock.acquire();
	}
	
	public void onPause(){
		super.onPause();
		
		wakeLock.release();
	}
	
	protected Dialog onCreateDialog(int id){
		if(id == 0){
			AlertDialog.Builder builder = new AlertDialog.Builder(VideoChatActivity.this);
			builder.setTitle(to + " has quitted, press back button to exit.");
			return builder.create();
		}
		
		return null;
	}
	
	private Handler handler = new Handler(){
		public void handleMessage(Message msg){
			switch(msg.what){
			case HandlerConstants.STOP_VIDEO_CHAT:
				firstTearDown = false;
				showDialog(0);
				break;
			case HandlerConstants.IMAGE_DECODED:
				Bitmap bitmap = (Bitmap) msg.obj;
				
				try {
					imgSem.acquire();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				imageView.setImageBitmap(bitmap);
				imageView.invalidate();
				imgSem.release();
				break;
			case TGDevice.MSG_STATE_CHANGE:
				switch(msg.arg1){
				case TGDevice.STATE_IDLE:
					break;
				case TGDevice.STATE_CONNECTING:
					break;
				case TGDevice.STATE_CONNECTED:
					Toast.makeText(getBaseContext(), "Connected", Toast.LENGTH_LONG).show();
					tgDevice.start();//start data flow
					break;
				case TGDevice.STATE_NOT_FOUND:
					Toast.makeText(getBaseContext(), "Not found", Toast.LENGTH_LONG).show();
					break;
				case TGDevice.STATE_NOT_PAIRED:
					Toast.makeText(getBaseContext(), "Not paired", Toast.LENGTH_LONG).show();
					break;
				case TGDevice.STATE_DISCONNECTED:
					Toast.makeText(getBaseContext(), "Disconnected", Toast.LENGTH_LONG).show();
					break;
				default:
					break;
				}
				
				break;
			case TGDevice.MSG_POOR_SIGNAL:
				break;
			case TGDevice.MSG_ATTENTION:
				int attention = msg.arg1;
				new Thread(new SendAttentionWorker(attention)).start();
				if(attention < 30){
					vibrator.vibrate(100);
					attentionIV.setBackgroundResource(R.drawable.emo_sad);
				} else {
					attentionIV.setBackgroundResource(R.drawable.emo_happy);
				}
				break;
			case TGDevice.MSG_MEDITATION:
				break;
			case TGDevice.MSG_RAW_DATA:
				break;
			case TGDevice.MSG_EEG_POWER:
				break;
			case TGDevice.MSG_BLINK:
				break;
			case TGDevice.MSG_LOW_BATTERY:
				Toast.makeText(getBaseContext(), "Low Battery!", Toast.LENGTH_LONG).show();
				break;
				
			case HandlerConstants.ATTENTION_RECVD:
				int value = ((Integer)msg.obj).intValue();
				if(value < 30){
					attentionIV.setBackgroundResource(R.drawable.emo_sad);
				} else {
					attentionIV.setBackgroundResource(R.drawable.emo_happy);
				}
				break;
			default:
				break;
			}
		}
	};
	
	public boolean onCreateOptionsMenu(Menu menu){
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.connect_menu, menu);
		return true;
	}
	
	public boolean onOptionsItemSelected(MenuItem item){
		switch(item.getItemId()){
		case R.id.connect:
			if(this.tgDevice.getState() != TGDevice.STATE_CONNECTING && this.tgDevice.getState() != TGDevice.STATE_CONNECTED){
				this.tgDevice.connect(true);
			}
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
	}
	
	private class SendAttentionWorker implements Runnable{
		private int result;
		
		public SendAttentionWorker(int result){
			this.result = result;
		}
		
		@Override
		public void run() {
			// TODO Auto-generated method stub
			if(attentionSocket == null){
				try {
					attentionSocket = new DatagramSocket();
				} catch (SocketException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
			attentionBuffer[0] = (byte) result;
			attentionBuffer[1] = (byte) (result >>> 8);
			attentionBuffer[2] = (byte) (result >>> 16);
			attentionBuffer[3] = (byte) (result >>> 24);
			
			try {
				DatagramPacket packet = new DatagramPacket(attentionBuffer, attentionBuffer.length, new InetSocketAddress(StateManager.getManager().getUserIP(to), SystemConstants.ATTENTION_PORT));
				attentionSocket.send(packet);
			} catch (SocketException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
}
